package com.cloud.common.feign;

import com.cloud.common.bean.ResultInfo;
import com.cloud.common.bean.SessionInfo;
import com.cloud.common.config.WebConfig;
import com.cloud.common.context.SessionContext;
import com.cloud.common.context.TraceContext;
import com.cloud.common.context.VersionContext;
import com.cloud.common.exception.AppException;
import com.cloud.common.mock.HttpMockInterceptor;
import com.cloud.common.support.BaseInterceptor;
import com.cloud.common.util.JsonUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;

@Slf4j
@Aspect
@Component
@ConditionalOnMissingClass("com.cloud.gateway.bean.GatewayInfo")
public class FeignInterceptor extends BaseInterceptor implements RequestInterceptor {
    private Set<String> services = new HashSet();

    @Autowired
    private HttpMockInterceptor mockInterceptor;

    @Autowired
    private FeignRpc feignRpc;

    /**
     * RequestInterceptor拦截处理的回调
     *
     * @param requestTemplate
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        if (requestTemplate != null) {
            String traceId = TraceContext.get();
            if (traceId != null) {
                requestTemplate.header(TraceContext.TRACE_ID, traceId);
            }
            SessionInfo sessionInfo = SessionContext.get();
            if (sessionInfo != null) {
                // 只传递关键信息
                SessionInfo transferSession = new SessionInfo();
                transferSession.setAppName(sessionInfo.getAppName());
                transferSession.setUserId(sessionInfo.getUserId());
                if (SessionInfo.DEBUG.equals(sessionInfo.getSecret())) {
                    transferSession.setSecret(sessionInfo.getSecret());
                }
                requestTemplate.header(SessionContext.SESSION_INFO, StringUtils.stripAll(JsonUtils.toJSONString(transferSession)));
            }
            String versionInfo = VersionContext.get();
            if (versionInfo != null) {
                requestTemplate.header(VersionContext.VERSION_INFO, versionInfo);
            }
            String xid = RootContext.getXID();
            if (StringUtils.isNotEmpty(xid)) {
                requestTemplate.header(RootContext.KEY_XID, xid);
            }
            requestTemplate.header(WebConfig.FROM_GATEWAY, Boolean.FALSE.toString());
        }
    }

    /**
     * AOP拦截处理的回调
     *
     * @param point
     * @return
     */
    @Around("@annotation(FeignInvoke)")
    public Object doFeign(ProceedingJoinPoint point) throws Throwable {
        // 获取Feign属性
        FeignAttribute feignAttribute = getFeignAttribute(point);

        // 判断是否Mock
        if (mockInterceptor.canMock(point, feignAttribute)) {
            return mockInterceptor.invokeMock(point, feignAttribute);
        }

        ResultInfo<?> resultInfo;
        if (feignAttribute.isRpc && services.contains(feignAttribute.serviceName)) {
            resultInfo = (ResultInfo<?>) execute(point, () -> feignRpc.invoke(point, feignAttribute));
        } else {
            // rpc首次调用，要初始化ribbon状态
            services.add(feignAttribute.serviceName);
            resultInfo = (ResultInfo<?>) execute(point, null);
        }
        if (resultInfo != null && resultInfo.successful()) {
            return resultInfo;
        }
        String detail = String.format("invoke serviceName=%s requestUri=%s message=%s params >>> %s",
                feignAttribute.serviceName, feignAttribute.getRequestUri(), resultInfo.getMessage(), getArgs(point));
        throw new AppException(resultInfo.getCode(), resultInfo.getMessage(), detail);
    }

    /**
     * 获取调用属性
     *
     * @param point
     * @return FeignAttribute
     */
    private FeignAttribute getFeignAttribute(ProceedingJoinPoint point) {
        // 获取调用属性
        String requestUri = null;
        FeignInvoke feignInvoke = null;
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Annotation[] annotations = methodSignature.getMethod().getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof FeignInvoke) {
                feignInvoke = (FeignInvoke) annotation;
            } else if (annotation instanceof GetMapping) {
                requestUri = ((GetMapping) annotation).value()[0];
            } else if (annotation instanceof PostMapping) {
                requestUri = ((PostMapping) annotation).value()[0];
            } else if (annotation instanceof PutMapping) {
                requestUri = ((PutMapping) annotation).value()[0];
            } else if (annotation instanceof DeleteMapping) {
                requestUri = ((DeleteMapping) annotation).value()[0];
            } else if (annotation instanceof RequestMapping) {
                requestUri = ((RequestMapping) annotation).value()[0];
            }
        }
        if (requestUri == null) {
            throw new RuntimeException("feign client not found request uri");
        }
        if (feignInvoke == null) {
            throw new RuntimeException("feign client not found @FeignInvoke annotation");
        }
        boolean isRpc = feignInvoke.rpc();
        int timeout = feignInvoke.timeout();

        // 获取调用服务名
        Class<?> classTarget = point.getTarget().getClass().getInterfaces()[0];
        String serviceName = classTarget.getAnnotation(FeignClient.class).name();
        return new FeignAttribute(serviceName, requestUri, isRpc, timeout);
    }
}
